#ifndef GAMEENGINE_GEOMETRY_TRGL3D_DEFS_H
#define GAMEENGINE_GEOMETRY_TRGL3D_DEFS_H

#include <GameEngine/Geometry/geometry_settings.h>

//=================================================================================

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================

namespace Trgl3D {
	//! Point index along a triangle
	enum Index {
		//! First point
		/*!
		 @note This is point at parametric coordinates (0,0)
		 */
		First = 0,
		//! Second point
		/*!
		 @note This is point at parametric coordinates (1,0)
		 */
		Second = 1,
		//! Third point
		/*!
		 @note This is point at parametric coordinates (0,1)
		 */
		Third = 2
	};
}

//=================================================================================
// Set of macros for the Trgl3d and Trgl3dFast classes

//! Compute the point along the triangle at given parametric coordinates
#define GAMEENGINE_IMPLEMENT_TRGL3D_PARAMETRIC_POINT(pt0, pt1, pt2, coords, type) \
	return Point3d<type>( \
				pt0.x() + coords.u()*(pt1.x()-pt0.x()) + coords.v()*(pt2.x()-pt0.x()), \
				pt0.y() + coords.u()*(pt1.y()-pt0.y()) + coords.v()*(pt2.y()-pt0.y()), \
				pt0.z() + coords.u()*(pt1.z()-pt0.z()) + coords.v()*(pt2.z()-pt0.z()) \
			);

//=================================================================================

//! Compute the parametric coordinates along the triangle at given point
#define GAMEENGINE_IMPLEMENT_TRGL3D_PARAMETRIC_COORDS(pt, type) \
	Transformer3d<type> t = transformer(); \
	Point3d<type> tmp = t.transform(pt); \
	return Coords2d<type>(tmp.x(), tmp.y());

//=================================================================================

//! Compute a transformer that converts points into parametric coordinates along the triangle
#define GAMEENGINE_IMPLEMENT_TRGL3D_PARAMETRIC_TRANSFORMER(pt0, pt1, pt2, type) \
	Vector3d<type> n = normal(); \
	return Transformer3d<type>( \
		pt0, pt1, pt2, Point3d<type>(pt0.x()+n.x(),pt0.y()+n.y(),pt0.z()+n.z()), \
		Point3d<type>(0,0,0), Point3d<type>(1,0,0), Point3d<type>(0,1,0), Point3d<type>(0,0,1));

//=================================================================================

//! Test wether a point is inside the triangle plane
#define GAMEENGINE_IMPLEMENT_TRGL3D_COPLANAR(pt0, rhs, tolerance, type) \
	/* Get the triangle normal */ \
	Vector3d<type> n = normal(); \
	\
	/* Compute the dot product with the given point */ \
	type dot = n.dot(Vector3d<type>(pt0, rhs)); \
	\
	/* If the dot product is close enough to 0, point is considered coplanar with the triangle */ \
	return ( dot <= tolerance );

//=================================================================================

//! Test wether a point is inside the triangle
#define GAMEENGINE_IMPLEMENT_TRGL3D_INSIDE(pt0, pt1, pt2, rhs, strict, type) \
	/* Check whether point is in the triangle plane */ \
	if ( coplanar(rhs) == false ) return false; \
	\
	/* Get the triangle normal */ \
	Vector3d<type> n = normal(); \
	\
	/* Get the sides of the triangle */ \
	type d1 = n.dot( Vector3d<type>(pt0, pt1).cross(Vector3d<type>(pt0, rhs)) ); \
	type d2 = n.dot( Vector3d<type>(pt1, pt2).cross(Vector3d<type>(pt1, rhs)) ); \
	type d3 = n.dot( Vector3d<type>(pt2, pt0).cross(Vector3d<type>(pt2, rhs)) ); \
	\
	/* Check the triangle belonging */ \
	if ( strict ) { \
		/* Check whether point lies on one of the (extended) triangle edges */ \
		/* If it does, point does not strictly belong to the triangle */ \
		if ( d1 == 0 ) return false; \
		if ( d2 == 0 ) return false; \
		if ( d3 == 0 ) return false; \
		\
		/* Check the sides */ \
		if ( ( d1 < 0 ) != ( d2 < 0 ) ) return false; \
		if ( ( d1 < 0 ) != ( d3 < 0 ) ) return false; \
	} \
	else { \
		/* Get the first non-null dot product (i.e., the first edge along which point does not lie) */ \
		type d = ( d1 == 0 ? ( d2 == 0 ? d3 : d2 ) : d1 ); \
		\
		/* Check the sides if and only if point is not on the edge */ \
		if ( ( d1 != 0 ) && ( ( d <= 0 ) != ( d1 <= 0 ) ) ) return false; \
		if ( ( d2 != 0 ) && ( ( d <= 0 ) != ( d2 <= 0 ) ) ) return false; \
		if ( ( d3 != 0 ) && ( ( d <= 0 ) != ( d3 <= 0 ) ) ) return false; \
	} \
	\
	/* If all sides are the same, point is in the triangle */ \
	return true;

//=================================================================================

//! Compute the intersection between the line and the triangle
#define GAMEENGINE_IMPLEMENT_TRGL3D_INTERSECTION_LINE(pt0, pt1, pt2, rhs, line_coordinate, coordinates, intersection_type, type) \
	/* Compute denominator */ \
	type den = ( \
		  pt2.x()*((pt0.y() - pt1.y())*(rhs.p1().z() - rhs.p2().z()) - (rhs.p1().y() - rhs.p2().y())*(pt0.z() - pt1.z())) \
		+ pt1.x()*(-(pt0.y() - pt2.y())*(rhs.p1().z() - rhs.p2().z()) + (rhs.p1().y() - rhs.p2().y())*(pt0.z() - pt2.z())) \
		+ pt0.x()*((pt1.y() - pt2.y())*(rhs.p1().z() - rhs.p2().z()) - (rhs.p1().y() - rhs.p2().y())*(pt1.z() - pt2.z())) \
		+ (rhs.p1().x() - rhs.p2().x())*(pt2.y()*(pt0.z() - pt1.z()) + pt0.y()*(pt1.z() - pt2.z()) + pt1.y()*(-pt0.z() + pt2.z())) \
	); \
	\
	/* The line is parallel to the triangle */ \
	if ( den == 0 ) { \
		if ( intersection_type ) *intersection_type = IntersectionType::Parallel; \
		return false; \
	} \
	\
	/* Compute U coordinate */ \
	coordinates.u() = ( \
		  pt2.x()*(pt0.y()*(rhs.p1().z() - rhs.p2().z()) + rhs.p1().y()*(rhs.p2().z() - pt0.z()) + rhs.p2().y()*(-rhs.p1().z() + pt0.z())) \
		+ pt0.x()*(pt2.y()*(-rhs.p1().z() + rhs.p2().z()) + rhs.p2().y()*(rhs.p1().z() - pt2.z()) + rhs.p1().y()*(-rhs.p2().z() + pt2.z())) \
		+ rhs.p2().x()*(pt2.y()*(rhs.p1().z() - pt0.z()) + rhs.p1().y()*(pt0.z() - pt2.z()) + pt0.y()*(-rhs.p1().z() + pt2.z())) \
		+ rhs.p1().x()*(pt2.y()*(-rhs.p2().z() + pt0.z()) + pt0.y()*(rhs.p2().z() - pt2.z()) + rhs.p2().y()*(-pt0.z() + pt2.z())) \
	) / den; \
	\
	/* Compute V coordinate */ \
	coordinates.v() = -( \
		  pt1.x()*(pt0.y()*(rhs.p1().z() - rhs.p2().z()) + rhs.p1().y()*(rhs.p2().z() - pt0.z()) + rhs.p2().y()*(-rhs.p1().z() + pt0.z())) \
		+ pt0.x()*(pt1.y()*(-rhs.p1().z() + rhs.p2().z()) + rhs.p2().y()*(rhs.p1().z() - pt1.z()) + rhs.p1().y()*(-rhs.p2().z() + pt1.z())) \
		+ rhs.p2().x()*(pt1.y()*(rhs.p1().z() - pt0.z()) + rhs.p1().y()*(pt0.z() - pt1.z()) + pt0.y()*(-rhs.p1().z() + pt1.z())) \
		+ rhs.p1().x()*(pt1.y()*(-rhs.p2().z() + pt0.z()) + pt0.y()*(rhs.p2().z() - pt1.z()) + rhs.p2().y()*(-pt0.z() + pt1.z())) \
	) / den; \
	\
	/* Compute line coordinate */ \
	line_coordinate = ( \
		  pt2.x()*(pt1.y()*(-rhs.p1().z() + pt0.z()) + pt0.y()*(rhs.p1().z() - pt1.z()) + rhs.p1().y()*(-pt0.z() + pt1.z())) \
		+ pt1.x()*(pt2.y()*(rhs.p1().z() - pt0.z()) + rhs.p1().y()*(pt0.z() - pt2.z()) + pt0.y()*(-rhs.p1().z() + pt2.z())) \
		+ pt0.x()*(pt2.y()*(-rhs.p1().z() + pt1.z()) + pt1.y()*(rhs.p1().z() - pt2.z()) + rhs.p1().y()*(-pt1.z() + pt2.z())) \
		+ rhs.p1().x()*(pt2.y()*(pt0.z() - pt1.z()) + pt0.y()*(pt1.z() - pt2.z()) + pt1.y()*(-pt0.z() + pt2.z())) \
	) / den; \
	\
	/* Check if intersection occurs out of the triangle */ \
	if ( in_trgl(coordinates) == false ) { \
		if ( intersection_type ) *intersection_type = IntersectionType::ExternIntersection; \
		return false; \
	} \
	\
	/* Check if intersection occurs on the triangle edges */ \
	if ( intersection_type ) { \
		if ( in_trgl(coordinates, true) == false ) { \
			*intersection_type = IntersectionType::EdgeIntersection; \
		} \
		else *intersection_type = IntersectionType::Intersection; \
	} \
	return true;

//=================================================================================

//! Compute the parametric coordinates of the closest point along the triangle
#define GAMEENGINE_IMPLEMENT_TRGL3D_CLOSEST_POINT(pt0, pt1, pt2, rhs, coords, type) \
	/* Create a line with the normal */ \
	Vector3d<type> n = normal(); \
	Line3d<type> line(rhs, Point3d<type>(rhs.x()+n.x(), rhs.y()+n.y(), rhs.z()+n.z())); \
	\
	/* Compute the intersection between the line and the triangle */ \
	IntersectionType::Type intersection_type; \
	Coords2d<type> coordinates; \
	type line_coordinate; \
	bool test = intersection(line, line_coordinate, coordinates, &intersection_type); \
	if ( test == false ) { \
		coords = Coords2d<type>(0,0); \
		return false; \
	} \
	\
	/* Check if intersection point is in the triangle */ \
	if ( in_trgl(coordinates) ) { \
		coords = coordinates; \
		return true; \
	} \
	\
	/* If it is not in the triangle, find the closest triangle point */ \
	Point3d<type> i = point(coordinates); \
	\
	/* Find the two closest points */ \
	int index1 = 0, index2 = 1; \
	if ( i.get_square_distance(pt0) > i.get_square_distance(pt1) ) { \
		index1 = 1; \
		index2 = 0; \
	} \
	if ( i.get_square_distance(operator[]((Trgl3D::Index)index2)) > i.get_square_distance(pt2) ) { \
		index2 = 2; \
	} \
	\
	/* Test if point falls out of the line */ \
	Line3d<type> closest_line(operator[]((Trgl3D::Index)index1), operator[]((Trgl3D::Index)index2)); \
	type param = closest_line.closest_point(rhs); \
	if ( param <= 0 ) { \
		if ( index1 == 0 ) { \
			coords = Coords2d<type>(0,0); \
			return true; \
		} \
		if ( index1 == 1 ) { \
			coords = Coords2d<type>(1,0); \
			return true; \
		} \
		coords = Coords2d<type>(0,1); \
		return true; \
	} \
	if ( param >= 1 ) { \
		if ( index2 == 0 ) { \
			coords = Coords2d<type>(0,0); \
			return true; \
		} \
		if ( index2 == 1 ) { \
			coords = Coords2d<type>(1,0); \
			return true; \
		} \
		coords = Coords2d<type>(0,1); \
		return true; \
	} \
	\
	/* Test if point falls in the line */ \
	Coords2d<type> res(0,0); \
	if ( index1 == 1 ) res.u() += ( (type)1.0 - param ); \
	else if ( index1 == 2 ) res.v() += ( (type)1.0 - param ); \
	if ( index2 == 1 ) res.u() += param; \
	else if ( index2 == 2 ) res.v() += param; \
	\
	/* Return closest point */ \
	coords = res; \
	return true;

//=================================================================================

//! Compute the perimeter of the triangle
#define GAMEENGINE_IMPLEMENT_TRGL3D_PERIMETER(pt0, pt1, pt2, type) \
	return (type)(pt0.get_distance(pt1) + pt1.get_distance(pt2) + pt2.get_distance(pt0));

//=================================================================================

//! Compute the area of the triangle
#define GAMEENGINE_IMPLEMENT_TRGL3D_AREA(pt0, pt1, pt2, type) \
	return (type)( Vector3d<type>::cross( Vector3d<type>(pt0, pt1), Vector3d<type>(pt0, pt2) ).length() / 2.0 );

//=================================================================================

//! Compute the plane in which the triangle lies
#define GAMEENGINE_IMPLEMENT_TRGL3D_PLANE(pt0, pt1, pt2, type) \
	return Plane3d<type>(pt0, pt1, pt2);

//=================================================================================

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
